package org.yeasy.report.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import org.yeasy.common.util.CommonBeanUtil;
import org.yeasy.common.util.CustomDateUtil;
import org.yeasy.common.util.CustomDecimalUtil;
import org.yeasy.report.bean.ColumnSetting;
import org.yeasy.report.constant.ColumnType;
import org.yeasy.report.constant.DateConstant;
import org.yeasy.report.constant.MarkConstant;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * The type My report util.
 *
 * @author yangyishe
 * @date 2022年08月26日 10:01
 */
public class ReportUtil {

    /**
     * Calc 2 accumulation group month list.
     *
     * @param <T>               the type parameter
     * @param dataList          the data list
     * @param beforeData        the before data
     * @param columnSettingList the report column setting list
     * @return the list
     * @author yangyishe
     * @date 2022年09月05日 10:32
     */
    public static <T extends Serializable> List<T> calc2AccumulationGroupMonth(List<T> dataList,
                                                                               T beforeData,
                                                                               List<ColumnSetting> columnSettingList) {
        List<String> lstDateField = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.DATE);
        String strDateField = lstDateField.get(0);
        Function<T, Date> calcDate = o -> CustomDateUtil.convert2Date(CommonBeanUtil.invokeGetter(o, strDateField));

        List<T> lstSumGroup = calc2SumGroupMonth(dataList, columnSettingList);
        lstSumGroup = lstSumGroup.stream().sorted(Comparator.comparing(calcDate)).collect(Collectors.toList());

        Set<String> setCalcField = ColumnUtil.calc2FieldSet(columnSettingList, ColumnType.CALC);
        BinaryOperator<T> calcMerge = (t, t2) -> {
            T mMerge = ObjectUtil.clone(t);
            for (String calcField : setCalcField) {
                BigDecimal dec1 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t, calcField));
                BigDecimal dec2 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t2, calcField));
                CommonBeanUtil.invokeSetter(mMerge, calcField, dec1.add(dec2));
            }
            ColumnUtil.mergeObj(mMerge, t, t2, columnSettingList);
            Date mAnyDate = CustomDateUtil.convert2Date(CommonBeanUtil.invokeGetter(t2, strDateField));
            Date mMonthEnd = DateUtil.beginOfDay(DateUtil.endOfMonth(mAnyDate));
            CommonBeanUtil.invokeSetter(mMerge, strDateField, mMonthEnd);

            return mMerge;
        };

        List<T> lstAccumulation = new ArrayList<>();
        T mAccumulation = ObjectUtil.clone(beforeData);
        for (T mSum : lstSumGroup) {
            mAccumulation = calcMerge.apply(mAccumulation, mSum);
            lstAccumulation.add(ObjectUtil.clone(mAccumulation));
        }

        return lstAccumulation;
    }

    /**
     * Fill null month data list.填充空月份数据
     *
     * @param <T>               the type parameter
     * @param dataList          the data list
     * @param baseList          the base list
     * @param startDate         the start date
     * @param endDate           the end date
     * @param columnSettingList the report column setting list
     * @return the list
     * @author yangyishe
     * @date 2022年08月30日 14:57
     */
    public static <T extends Serializable> List<T> calc2FillEmptyGroupMonth(List<T> dataList, List<T> baseList,
                                                                            Date startDate, Date endDate,
                                                                            List<ColumnSetting> columnSettingList) {
        List<String> lstDateField = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.DATE);
        String strDateField = lstDateField.get(0);

        List<Date> lstMonth = new ArrayList<>();
        Date mNextMonth = DateUtil.beginOfMonth(startDate);
        while (!mNextMonth.after(DateUtil.beginOfMonth(endDate))) {
            lstMonth.add(mNextMonth);
            mNextMonth = DateUtil.offsetMonth(mNextMonth, 1);
        }

        List<String> lstCalcField = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.CALC);
        Function<T, String> calcUniqueKeyWithoutMonth = ColumnUtil.calc2GroupKeyFunc(columnSettingList);
        Function<T, String> calcUniqueKeyWithMonth = t -> {
            Date mMonth = CommonBeanUtil.invokeGetter(t, strDateField);
            String strMonth = DateUtil.format(mMonth, DateConstant.DEFAULT_MONTH_PATTERN);
            return calcUniqueKeyWithoutMonth.apply(t) + MarkConstant.DEFAULT_UNIQUE_CHAIN_SEPARATOR + strMonth;
        };
        Map<String, T> mapKey2Data = dataList.stream().collect(Collectors.toMap(calcUniqueKeyWithMonth, o -> o));

        List<T> lstResult = new ArrayList<>();
        for (T mBase : baseList) {
            for (Date mMonth : lstMonth) {
                String strKeyIncludeMonth = calcUniqueKeyWithoutMonth.apply(mBase)
                        + MarkConstant.DEFAULT_UNIQUE_CHAIN_SEPARATOR
                        + DateUtil.format(mMonth, DateConstant.DEFAULT_MONTH_PATTERN);
                T mEach = mapKey2Data.get(strKeyIncludeMonth);
                if (mEach == null) {
                    mEach = ObjectUtil.clone(mBase);
                    CommonBeanUtil.invokeSetter(mEach, strDateField, mMonth);
                }
                for (String s : lstCalcField) {
                    if (CommonBeanUtil.invokeGetter(mEach, s) == null) {
                        CommonBeanUtil.invokeSetter(mEach, s, BigDecimal.ZERO);
                    }
                }
                lstResult.add(mEach);
            }
        }

        return lstResult;
    }

    /**
     * Calc merge group list.根据any原则合并组(只要是非空,不限field类型)
     *
     * @param <T>               the type parameter
     * @param dataList          the data list
     * @param columnSettingList the report column setting list
     * @return the list
     * @author yangyishe
     * @date 2022年08月30日 14:10
     */
    public static <T extends Serializable> List<T> calc2MergeAny(List<T> dataList, List<ColumnSetting> columnSettingList) {
        // 获取group key集合
        List<String> lstMergeField = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.GROUP);
        Function<T, String> calcMergeKey = t -> lstMergeField.stream()
                .map(field -> CommonBeanUtil.invokeGetter(t, field).toString())
                .reduce("", (a, b) -> (a + MarkConstant.DEFAULT_UNIQUE_CHAIN_SEPARATOR + b))
                .trim();

        // 分别组成各个map
        Map<String, List<T>> mapMergeKey2DataList = new HashMap<>();
        for (T obj : dataList) {
            String strMergeKey = calcMergeKey.apply(obj);
            mapMergeKey2DataList.putIfAbsent(strMergeKey, new ArrayList<>());
            mapMergeKey2DataList.get(strMergeKey).add(obj);
        }

        return mapMergeKey2DataList.values().stream()
                .map(lst -> lst.stream()
                        .reduce(ReportBeanUtil::mergeObject).get())
                .collect(Collectors.toList());
    }

    /**
     * Calc 2 base list.计算基底数据(暂不考虑明细合并问题)
     *
     * @param <T>               the type parameter
     * @param dataList          the data list
     * @param columnSettingList the report column setting list
     * @param keepPre           the keep pre
     * @return the list
     * @author yangyishe
     * @date 2022年09月01日 16:20
     */
    public static <T extends Serializable> List<T> calc2MixBase(List<T> dataList, List<ColumnSetting> columnSettingList, boolean keepPre) {
        List<String> lstGroupSetting = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.GROUP);
        Function<T, String> calcGroupKey = t -> lstGroupSetting.stream()
                .map(s -> CommonBeanUtil.invokeGetter(t, s).toString())
                .reduce("", (a, b) -> (a + MarkConstant.DEFAULT_UNIQUE_CHAIN_SEPARATOR + b))
                .trim();
        Map<String, T> mapKey2Data = new HashMap<>();
        for (T mData : dataList) {
            String strKey = calcGroupKey.apply(mData);
            if (keepPre) {
                mapKey2Data.putIfAbsent(strKey, mData);
            } else {
                mapKey2Data.put(strKey, mData);
            }

        }

        return mapKey2Data.values().stream().map(ObjectUtil::clone).collect(Collectors.toList());
    }


    /**
     * Merge 2 begin group list.合并期初组
     *
     * @param <T>               the type parameter
     * @param initList          the init list 注:initList需要称为基底.否则需要先计算基底.
     * @param beforeList        the before list
     * @param columnSettingList the report column setting list
     * @return the list
     * @author yangyishe
     * @date 2022年09月01日 09:32
     */
    public static <T extends Serializable> List<T> calc2SumGroupBegin(List<T> initList, List<T> beforeList, List<ColumnSetting> columnSettingList) {
        Function<T, String> calcGroupKey = ColumnUtil.calc2GroupKeyFunc(columnSettingList);
        Map<String, T> mapKey2Before = beforeList.stream().collect(Collectors.toMap(calcGroupKey, o -> o));

        List<String> lstCalcField = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.CALC);
        BinaryOperator<T> funcMerge = (t, t2) -> {
            for (String calcField : lstCalcField) {
                BigDecimal dec1 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t, calcField));
                BigDecimal dec2 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t2, calcField));
                CommonBeanUtil.invokeSetter(t, calcField, dec1.add(dec2));
            }
            return t;
        };
        List<T> lstResult = new ArrayList<>();
        for (T mInit : initList) {
            T mNewInit = ObjectUtil.clone(mInit);
            T mBefore = mapKey2Before.get(calcGroupKey.apply(mNewInit));
            if (mBefore != null) {
                lstResult.add(funcMerge.apply(mNewInit, mBefore));
            } else {
                lstResult.add(mNewInit);
            }
        }

        return lstResult;
    }

    /**
     * Calc 2 sum group month list.
     *
     * @param <T>               the type parameter
     * @param dataList          the data list
     * @param columnSettingList the report column setting list
     * @return the list
     * @author yangyishe
     * @date 2022年09月05日 10:19
     */
    public static <T extends Serializable> List<T> calc2SumGroupMonth(List<T> dataList,
                                                                      List<ColumnSetting> columnSettingList) {
        List<String> lstDateField = ColumnUtil.calc2FieldList(columnSettingList, ColumnType.DATE);
        String strDateField = lstDateField.get(0);

        Map<String, List<T>> mapMonth2DataList = new HashMap<>();
        for (T mData : dataList) {
            Date mOpeningDate = CustomDateUtil.convert2Date(CommonBeanUtil.invokeGetter(mData, strDateField));
            String strMonth = DateUtil.format(mOpeningDate, DateConstant.DEFAULT_MONTH_PATTERN);
            mapMonth2DataList.putIfAbsent(strMonth, new ArrayList<>());
            mapMonth2DataList.get(strMonth).add(mData);
        }

        Set<String> setCalcField = ColumnUtil.calc2FieldSet(columnSettingList, ColumnType.CALC);
        BinaryOperator<T> calcMerge = (t, t2) -> {
            T mMerge = ObjectUtil.clone(t);
            for (String calcField : setCalcField) {
                BigDecimal dec1 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t, calcField));
                BigDecimal dec2 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t2, calcField));
                CommonBeanUtil.invokeSetter(mMerge, calcField, dec1.add(dec2));
            }
            ColumnUtil.mergeObj(mMerge, t, t2, columnSettingList);
            Object mAnyDateFirst = CommonBeanUtil.invokeGetter(t2, strDateField);
            Date mAnyDate = CustomDateUtil.convert2Date(mAnyDateFirst);
            Date mMonthEnd = DateUtil.beginOfDay(DateUtil.endOfMonth(mAnyDate));
            CommonBeanUtil.invokeSetter(mMerge, strDateField, mMonthEnd);

            return mMerge;
        };

        List<T> lstResult = new ArrayList<>();
        for (List<T> objListEach : mapMonth2DataList.values()) {
            Optional<T> optSum = objListEach.stream().reduce(calcMerge);
            optSum.ifPresent(t -> lstResult.add(ObjectUtil.clone(t)));
        }

        return lstResult;
    }

    /**
     * Calc 2 sum group setting list.
     *
     * @param <T>               the type parameter
     * @param dataList          the data list
     * @param columnSettingList the report column setting list
     * @return the list
     * @author yangyishe
     * @date 2022年09月05日 10:30
     */
    public static <T extends Serializable> List<T> calc2SumGroupSetting(List<T> dataList,
                                                                        List<ColumnSetting> columnSettingList) {
        Set<String> setGroupField = ColumnUtil.calc2FieldSet(columnSettingList, ColumnType.GROUP);
        Function<T, String> calcGroupKey = t -> setGroupField.stream()
                .map(s -> CommonBeanUtil.invokeGetter(t, s).toString())
                .reduce("", (a, b) -> (a + MarkConstant.DEFAULT_UNIQUE_CHAIN_SEPARATOR + b).trim());
        Map<String, List<T>> mapKey2List = new HashMap<>();
        for (T t : dataList) {
            String strKey = calcGroupKey.apply(t);
            mapKey2List.putIfAbsent(strKey, new ArrayList<>());
            mapKey2List.get(strKey).add(t);
        }

        Set<String> setCalcField = ColumnUtil.calc2FieldSet(columnSettingList, ColumnType.CALC);
        BinaryOperator<T> calcMerge = (t, t2) -> {
            T mMerge = ObjectUtil.clone(t);
            for (String calcField : setCalcField) {
                BigDecimal dec1 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t, calcField));
                BigDecimal dec2 = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t2, calcField));
                CommonBeanUtil.invokeSetter(mMerge, calcField, dec1.add(dec2));
            }
            ColumnUtil.mergeObj(mMerge, t, t2, columnSettingList);

            return mMerge;
        };

        List<T> lstResult = new ArrayList<>();
        for (List<T> objListEach : mapKey2List.values()) {
            Optional<T> optSum = objListEach.stream().reduce(calcMerge);
            optSum.ifPresent(t -> lstResult.add(ObjectUtil.clone(t)));
        }

        return lstResult;
    }

    /**
     * Calc accumulation.计算累计
     *
     * @param <T>               the type parameter
     * @param processList       the process list
     * @param beginList         the begin list
     * @param columnSettingList the report column setting list
     * @author yangyishe
     * @date 2022年09月01日 16:20
     */
    public static <T extends Serializable> void calcAccumulation(List<T> processList, List<T> beginList, List<ColumnSetting> columnSettingList) {
        Function<T, String> calcUniqueKey = ColumnUtil.calc2GroupKeyFunc(columnSettingList);
        Map<String, List<T>> mapKey2List = new HashMap<>();
        for (T mProcess : processList) {
            String strUniqueKey = calcUniqueKey.apply(mProcess);
            mapKey2List.putIfAbsent(strUniqueKey, new ArrayList<>());
            mapKey2List.get(strUniqueKey).add(mProcess);
        }

        List<ColumnSetting> lstCalcSetting = columnSettingList.stream()
                .filter(o -> CharSequenceUtil.isNotEmpty(o.getAccumulationField()))
                .collect(Collectors.toList());
        for (ColumnSetting mSetting : lstCalcSetting) {
            String strField = mSetting.getField();
            String strAccumulationField = mSetting.getAccumulationField();
            for (T t : beginList) {
                BigDecimal decAccumulationValue = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(t, strField));
                CommonBeanUtil.invokeSetter(t, strAccumulationField, decAccumulationValue);

                String strUniqueKey = calcUniqueKey.apply(t);
                List<T> lstProcessEach = mapKey2List.getOrDefault(strUniqueKey, new ArrayList<>());
                for (T mProcess : lstProcessEach) {
                    BigDecimal decEach = CustomDecimalUtil.convert2BigDecimal(CommonBeanUtil.invokeGetter(mProcess, strField));
                    decAccumulationValue = decAccumulationValue.add(decEach);
                    CommonBeanUtil.invokeSetter(mProcess, strAccumulationField, decAccumulationValue);
                }
            }
        }
    }


    /**
     * Fill default value.填充默认值
     *
     * @param <T>               the type parameter
     * @param obj               the obj
     * @param columnSettingList the report column setting list
     * @param forceUpdate       the force update
     * @author yangyishe
     * @date 2022年08月26日 11:36
     */
    public static <T extends Serializable> void fillDefaultValue(T obj, List<ColumnSetting> columnSettingList, boolean forceUpdate) {
        List<String> lstFillKey = columnSettingList.stream()
                .filter(o -> o.getType().equals(ColumnType.CALC))
                .map(ColumnSetting::getField).collect(Collectors.toList());
        for (String key : lstFillKey) {
            BigDecimal decVal = CommonBeanUtil.invokeGetter(obj, key);
            if (forceUpdate || decVal == null) {
                CommonBeanUtil.invokeSetter(obj, key, BigDecimal.ZERO);
            }
        }
    }

    /**
     * Fill default value batch.批量填充默认值
     *
     * @param <T>               the type parameter
     * @param objList           the obj list
     * @param columnSettingList the report column setting list
     * @param forceUpdate       the force update
     * @author yangyishe
     * @date 2022年08月26日 14:06
     */
    public static <T extends Serializable> void fillDefaultValueBatch(List<T> objList, List<ColumnSetting> columnSettingList, boolean forceUpdate) {
        List<String> lstFillKey = columnSettingList.stream()
                .filter(o -> o.getType().equals(ColumnType.CALC))
                .map(ColumnSetting::getField).collect(Collectors.toList());
        for (T obj : objList) {
            for (String key : lstFillKey) {
                BigDecimal decVal = CommonBeanUtil.invokeGetter(obj, key);
                if (forceUpdate || decVal == null) {
                    CommonBeanUtil.invokeSetter(obj, key, BigDecimal.ZERO);
                }
            }
        }
    }

}
